import java.util.List;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;



/*
 * Query -- Class -- performs the query of the word or words provided.  
 * 
 * Multiple queries may be performed sequentially. 
 * Queries do not augment a database's content apart from incrementing the number of times the word has been queried. 
 * Singleton objects are created for classes which need to be instantiated to support queries. 
 * 
 */
public class QueryDB {

	private static boolean initialized = false;
	private static WordListManager wlm = null;
	private static StopWord sw = null;

	/*
	 * Queries for a single word.  
	 */
	public static String performQuery(File collection, String word, int numResults) {
		if(!initialized) initialize();

		//stop words and non words do not generate results.  
		if(word == null || sw.isStopWord(word))
			return "No Results Found.\n";
		
		//clean the word for compatibility.  
		word = wlm.cleanWord(word);
		
		ArrayList<WordTile> listing = wlm.getWordTiles(word);
		if(listing == null) //no results for the word
			return "No Results Found.\n";
		
		StringBuffer buffer = new StringBuffer("");
		int freq = WordListManager.getFrequency();
		buffer.append("'").append(word).append("'");
		buffer.append(" has been searched for ").append(freq).append(" times");
		buffer.append(" in collection: '").append(collection.getPath()).append("'\n");
		
		Map<String, QueryResult> results = new HashMap<String, QueryResult>();  //used to map tiles into results, multiple tiles per result. 
		  
		//the tiles are added to the singular result that has been made for the docID:paragraphID key.  
		for(WordTile tile: listing){ //process listings into results, words with same paragraph generate same result.  
			String key = tile.getdocId()+":"+tile.getparaId(); // this is unique per paragraph. 
			QueryResult result = results.get(key);
			if(result == null){  //if the result does not exist, make it, otherwise use it.  
				result = new QueryResult(collection);
				results.put(key, result);
			}
			result.addtile(tile);
		}
		
		
		//arrange results in a list and sort based on comparison for ranking.  
		ArrayList<QueryResult> resultlist = new ArrayList<QueryResult>(results.values());
		Collections.sort(resultlist, new Comparator<QueryResult>(){
			@Override
			public int compare(QueryResult arg0, QueryResult arg1) {
				return arg1.compareTo(arg0);
			}});
		
		
		if(numResults == 0)
			numResults = resultlist.size();
		else if (numResults > resultlist.size())
			numResults = resultlist.size();
			
		for(int i = 0; i < numResults; i++){
			//process each result for display.
			buffer.append("\n").append(resultlist.get(i).getText());
		}
		
		return buffer.toString();
	}
	
	
	public static String performMQuery(File collection, String word1, String word2, int numResults) {
		if(!initialized) initialize();
		
		//stop words and non words do not generate results.  
		if(word1 == null || sw.isStopWord(word1))
			return "No Results Found.\n";
		if(word2 == null || sw.isStopWord(word2))
			return "No Results Found.\n";
		
		//clean the word for compatibility.  
		word1 = wlm.cleanWord(word1);
		word2 = wlm.cleanWord(word2);
		
		StringBuffer buffer = new StringBuffer("");

		ArrayList<WordTile> listing1 = wlm.getWordTiles(word1);
		if (listing1 == null) return "No Results Found.\n";
		int freq1 = WordListManager.getFrequency();
		buffer.append("'").append(word1).append("'");
		buffer.append(" has been searched for ").append(freq1).append(" times");
		buffer.append(" in collection: '").append(collection.getPath()).append("'\n");
		
		
		ArrayList<WordTile> listing2 = wlm.getWordTiles(word2);
		if (listing2 == null) return "No Results Found.\n";
		int freq2 = WordListManager.getFrequency();
		buffer.append("'").append(word2).append("'");
		buffer.append(" has been searched for ").append(freq2).append(" times");
		buffer.append(" in collection: '").append(collection.getPath()).append("'\n");
		
		
		Map<String, QueryResult> results = new HashMap<String, QueryResult>();  //used to map tiles into results, multiple tiles per result. 
		  
		//the tiles of listing1 are added to the singular result that has been made for the docID:paragraphID key.  
		for(WordTile tile: listing1){ //process listings into results, words with same paragraph generate same result.  
			String key = tile.getdocId()+":"+tile.getparaId(); // this is unique per paragraph. 
			QueryResult result = results.get(key);
			if(result == null){  //if the result does not exist, make it, otherwise use it.  
				result = new QueryResult(collection);
				results.put(key, result);
			}
			result.addtile(tile);
		}
		
		//the tiles of listing2 are added to the singular result that has been made for the docID:paragraphID key.  
		for(WordTile tile: listing2){ //process listings into results, words with same paragraph generate same result.  
			String key = tile.getdocId()+":"+tile.getparaId(); // this is unique per paragraph. 
			QueryResult result = results.get(key);
			if(result == null){  //if the result does not exist, make it, otherwise use it.  
				result = new QueryResult(collection);
				results.put(key, result);
			}
			result.addtile(tile);
		}
		
		
		//arrange results into a list and remove results which do not match the mquery.  
		String query = QueryDB.queryAND(word1,word2);  //make a query string of words, used for pruning results. 
		List<QueryResult> resultlist = new LinkedList<QueryResult>(results.values());
		Iterator<QueryResult> iterator = resultlist.iterator();
		while(iterator.hasNext()){ // removes any results which do not match the query.  
			QueryResult result = iterator.next();
			if(! result.matchesQuery(query))
				iterator.remove();
		}
		
		
		
		//arrange results in a list and sort based on comparison for ranking.  
		Collections.sort(resultlist, new Comparator<QueryResult>(){
			@Override
			public int compare(QueryResult arg0, QueryResult arg1) {
				return arg1.compareTo(arg0);
			}});
		
		
		if(numResults == 0)
			numResults = resultlist.size();
		else if (numResults > resultlist.size())
			numResults = resultlist.size();
			
		for(int i = 0; i < numResults; i++){
			//process each result for display.
			buffer.append("\n").append(resultlist.get(i).getText()).append("\n");
		}
		return buffer.toString();
	}

	//ONLY GOOD FOR PROCESSING ANDs, not ORs. 
	//returns the and of the two queries 
	//order of conjuncts is alphabetical to represent a canonical form.  
	public static String queryAND(String query1, String query2) {

		//if either operand is null, return the other operand
		if(query1 == null)
			return query2;
		if(query2 == null)
			return query1;
		
		//same query returns same query
		if(query1.compareTo(query2) == 0)
			return query1;
		
		//if one query contains the other, return the larger query.
		if(query1.length() > query2.length()){
			if(query1.contains(query2))
				return query1;
		} else {
			if(query2.contains(query1))
				return query2;
		}
	
		//take queries apart if they are conjunctions and return the conjunction of all the query operands. 
		Set<String> conjuncts = new HashSet<String>(); 
		for(String conjunct: query1.split(":"))
			conjuncts.add(conjunct);
		for(String conjunct: query2.split(":"))
			conjuncts.add(conjunct);
		StringBuffer newQuery = new StringBuffer();
		ArrayList<String> conjunctList = new ArrayList<String>();
		conjunctList.addAll(conjuncts);
		Collections.sort(conjunctList);
		for(String conjunct: conjunctList)
			newQuery.append(conjunct).append(":");
		newQuery.deleteCharAt(newQuery.length()-1); //deletes the last instance of ':'.  
		
		return newQuery.toString();
	}


	public static void initialize(){
		if(initialized) return;
		wlm = new WordListManager();
		sw = new StopWord();

		initialized = true;
	}

}
